package com.ordobill.webapp.common;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ordobill.webapp.beans.AvailableFields;
import com.ordobill.webapp.beans.Figures;
import com.ordobill.webapp.beans.Modeling;
import com.ordobill.webapp.beans.Project;
import com.ordobill.webapp.beans.SimulationFields;
import com.ordobill.webapp.engine.SimulationEngine;

/**
 * SimulationEngine클래스를 구현하고 Engine을 완성합니다.
 * 
 * @author Choi Jin Wook(A.K.A Brian Choi @ Ordobill Office) / choijinwook84@gmail.com
 *
 */
public class SimulationEngineImpl extends SimulationEngine{
    
    private Log log = LogFactory.getFactory().getInstance(this.getClass().getName());    
    
    /**
     * SimualtionEngineImpl 의 생성자를 정의합니다.
     * Project 객체를 받아 SimulationEngine의 기본 변수들을
     * 초기화 합니다.
     * @param project
     */
    public SimulationEngineImpl(Project project){
        super(project);
    }
    
    /**
     * SimulationEngineImpl의 기본생성자입니다.
     */
    public SimulationEngineImpl(){}
    

    /**
     * {@inheritDoc}
     * @deprecated for packing Figures per Equipment and probably doing Thread programing replaced by {@link #simulationBody(Vector)}
     */
    @Override
    @Deprecated public SimulationFields[] simulationBody(ArrayList<Figures> figList) throws Exception{
        /**
         * Initialization return object Arrays
         */
        SimulationFields[] simFieldsArr = new SimulationFields[getSimTime()];
        
        /**
         * View for simulation start time (Only View on command line)
         */
        long start = System.currentTimeMillis();

        /**
         * 각 수치값에서 나오는 결과를 저장하는 String Array 입니다.
         */
        String[] equipInform = new String[figList.size()];
        
        /**
         * Running Simulation Times
         */
        for(int strtSim = 0; strtSim < getSimTime(); strtSim++){
            /**
             * Initialize Simulation Fields Object
             */
            SimulationFields simFields = new SimulationFields();
            
            /**
             * Initialize present
             */
            float present = getStrTime();
            
            /**
             * Initialize repair time
             */
            float repairTime = 0f;

            ArrayList<Float> presentList = new ArrayList<Float>();
            

            /**
             * Testing gear in Life Times
             */
            //Start while syntax
            while(present < getLifeTime()){
                /**
                 * Setting Present to parent instance
                 */
                super.setPresent(present);
                
                /**
                 * Initialize MTTR ArrayList<Float>
                 */
                ArrayList<Float> mttr = new ArrayList<Float>(figList.size());
                
                /**
                 * Initialize System State
                 */
                int systemState = 1;
                
                /**
                 * Initialize figIndex
                 */
                int figIndx = 0;
                
                //Start For syntax
                for(Figures fig : figList){

                    float MTTR = 0f;
                    float hazardRate = selectFailType(fig);
                    float ran        = (float)Math.random();
                    equipInform[figIndx] = fig.getFigUid()+","+hazardRate+","+ran;
                    
                    /**
                     * 장비가 해당하는 FM모드에서 고장났는지를
                     * 분별하는 구문입니다. Monte Carlo 공식이 적용되는 부분입니다.
                     * Hazard Rate가 난수보다 작으면 정상이고, 난수보다 크면 장비는 고장나게 됩니다.
                     */
                    if(ran > hazardRate){

                        systemState = 1;
                        MTTR = 0f;
                    
                    }else{
                        systemState = 0;
//                        if(fig.getFigConnect() == 1){
                            
                            /**
                             * 수리시간을 얻어 옵니다.
                             */
                            MTTR = fig.getFigMttr().floatValue();
                            /**
                             * 고장 시점을 Fail Mode에 기록합니다.
                             */
                            fig.setFigBreakPoint(present+(MTTR/8760f));
                            
                            /**
                             * 고장 시점에 작용될 Reduction Factor를 기록합니다.
                             */
                            fig.setFigRecovFactor(fig.getFigImpRecov());
                            
//                        }else{
                            MTTR = 0f;
//                        }
                    }
                    mttr.add(MTTR);
                    systemState *= systemState;
                    figIndx++;
                    
                //End of For syntax
                }
                
                /**
                 * Max MTTR divided by 8760(Hour multiplied by 365(A year)
                 */
                float maxMTTR = (Collections.max(mttr)/8760f);
                
                //Initialize ArrayList Object for memory
                mttr = null;
                
                repairTime += maxMTTR;
                
                //if systems is available then
                if(systemState == 1){
                    present += (1f/getTimeDiv());
                //or else
                }else{
                    present += maxMTTR;
                }
                
            //End of While syntax
            }

            /**
             * 부모 클래스에 각 장비의 FM마다 생성되는
             * Hazard Rate와 난수를 저장합니다.
             */
            super.returnHazardRandom(equipInform);
            
            presentList.add(present);
            float availability = (1f-(repairTime/present))*100;
            
            
            Float[] timeSim = new Float[presentList.size()];
            //Converting ArrayList with Arrays
            presentList.toArray(timeSim);
            //Initialize ArrayList Object for memory
            presentList = null;
            
            simFields.setIterationStep(strtSim);
            simFields.setRepairTime(repairTime);
            simFields.setAvailabilityValue(availability);
            simFields.setTimeOfSimulation(timeSim);
            
            simFieldsArr[strtSim] = simFields;
            
            //Initialize Object for memory
            simFields = null;
            
            for(Figures fig : figList){
                fig.setFigBreakPoint(0f);
                fig.setFigRecovFactorIniti();
            }
            
        //End of for syntax
        }
        
        long end = System.currentTimeMillis();
        log.debug( "실행 시간 : " + ( end - start )/1000.0 + "초" );
        //System.out.println( "실행 시간 : " + ( end - start )/1000.0 + "초" );
        
        return simFieldsArr;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SimulationFields[] simulationBody(Vector<Modeling> modelingVector) throws Exception{
        
        //Initialization return object Arrays
        SimulationFields[] simFieldsArr = new SimulationFields[getSimTime()];
        
        // View for simulation start time (Only View on command line)
        long start = System.currentTimeMillis();

        int figuresCount = 0;
        
        for(Modeling mod : modelingVector){
            for(Figures fig : mod.getFiguresVector()){
                figuresCount++;
            }
        }
        
        //각 수치값에서 나오는 결과를 저장하는 String Array 입니다.
        String[] equipInform = new String[figuresCount];
        
        //
        ArrayList<BigDecimal> availableAverageList = new ArrayList<BigDecimal>();
        
        //Running Simulation Times
        for(int strtSim = 0; strtSim < getSimTime(); strtSim++){
            
            //Initialize Simulation Fields Object
            SimulationFields simFields = new SimulationFields();
            
            //Initialize present
            float present = getStrTime();
            
            //Initialize repair time
            float repairTime = 0f;

            //Initialize present list
            ArrayList<Float> presentList = new ArrayList<Float>();
            
            //Initialize available per equipment list
            ArrayList<AvailableFields> availableEquipmentList = new ArrayList<AvailableFields>();

            //Start while syntax
            while(present < getLifeTime()){
                
                //Setting Present to parent instance
                super.setPresent(present);
                
                //Initialize MTTR ArrayList<Float>
                ArrayList<Float> mttr = new ArrayList<Float>();
                
                //Initialize System State
                int systemState = 1;
                
                //Initialize figIndex
                int figIndx = 0;
                
                //Initialize Equipment for Available per a 1/timeDiv
                BigDecimal equipmentAvailable = new BigDecimal(1f);
                
                //Start equipment's for syntax 
                for(Modeling mod : modelingVector){
                    
                    //Start calculator for modeling type 'eq'
                    if(mod.getMoType().equals("eq")){
                        
                        //get modeling's figures
                        Vector<Figures> figVector = mod.getFiguresVector(); 
                        
                        //Initialize Figures  for Available per a 1/timeDiv
                        BigDecimal figuresAvailable = new BigDecimal(0);
                        
                        //Start For syntax
                        for(Figures fig : figVector){
                            
                            //Check it figure's state. true : normality, false : failure
                            if(fig.isFigStates()){
                                
                                //Mean Time To Repair
                                float MTTR           = 0f;
                                
                                //Getting Harzard Rate
                                float hazardRate     = selectFailType(fig);
                                
                                //Setting Random Number
                                float ran            = (float)Math.random();
                                
                                //Collecting for Simulation Display
                                equipInform[figIndx] = fig.getFigUid()+","+hazardRate+","+ran;
                                
                                /*
                                 * 장비가 해당하는 FM모드에서 고장났는지를
                                 * 분별하는 구문입니다. Monte Carlo 공식이 적용되는 부분입니다.
                                 * Hazard Rate가 난수보다 작으면 정상이고, 난수보다 크면 장비는 고장나게 됩니다.
                                 */
                                if(ran > hazardRate){
                                    figuresAvailable = new BigDecimal(1f);
                                    systemState = 1;
                                    MTTR = 0f;
                                
                            //End of if syntax    
                            }else{
                                    
                                    //To Set system state when break down 
                                    systemState = 0;
                                    
                                    //if modeling's pu connecting one
                                    if(mod.getMoPuConnect() == 1){
                                        
                                        //To Set Figure's state
                                        fig.setFigStates(false);
                                        
                                        //입력되어있는 1년 평균 수리시간을 년단위 비율로 환산합니다.
                                        MTTR = (fig.getFigMttr().floatValue()/8760f);
                                        
                                        //수리가 끝나는 시점을 구합니다.
                                        float finishedTime = present + MTTR;
                                        
                                        /*
                                         * 복구되는 시점이 1/TimeDivision 보다 클경우
                                         * 나머지를 구합니다.
                                         */
                                        float pieceTime = finishedTime%(1f/getTimeDiv());
                                        
                                        //Check next finish time over the life time
                                        if(finishedTime < getLifeTime()){
                                            
                                            //수리가 끝나는 시점을 저장합니다.
                                            fig.setFigFinishRepairTime(new BigDecimal(finishedTime));
                                            
                                            /*
                                             * (1/timeDiv)만큼 시간이 갈 때, 남게 되는 시간을 저장합니다.
                                             * 예를 들어 (1/timeDiv)가 0.002739 일 때, 수리 시점이 0.019178 이라고 가정하면
                                             * (1/timeDiv)가 7번 후에는 수리가 완료 된다. 하지만 수리시점이 0.020131 라고 하면
                                             * (1/timeDiv)가 7.35 후에야 수리가 완료 된다. 여기에서 0.35만큼은 따로 계산 해야 함으로
                                             * 나머지 값을 저장하여 계산을 돕습니다. 
                                             */
                                            fig.setFigPieceRepairTime(new BigDecimal(pieceTime));
                                            
                                            //배달 시점을 저장합니다.
                                            fig.setFigFinishDeliveryTime(new BigDecimal(present+(fig.getFigDelivery().floatValue()/getTimeDiv())));
                                        
                                        }
                                        
                                        //고장 시점을 Fail Mode에 기록합니다.
                                        fig.setFigBreakPoint(present);
                                        
                                        //고장 시점에 작용될 Reduction Factor를 기록합니다.
                                        fig.setFigRecovFactor(fig.getFigImpRecov());
                                        
                                        //고장난 시점에도 가용성을 측정하여야 함으로 아래 코드를 추가하였습니다.
                                        //고장난 현재 시점을 부품조달 시간과 비교합니다.
//                                        if(present < fig.getFigFinishDeliveryTime().floatValue()){
//                                            
//                                            //부품조달 중에는 1-MTTF impact(참고자료 1-2 2번 케이스)를 합니다.
//                                        	figuresAvailable = new BigDecimal(1f-fig.getFigImpFailure().floatValue());
//                                            
//                                        //고장난 현재 시점이 부품조달 시간보다 크거나 같다면 즉 배달시간이 없거나 배달을 했다면
//                                        }else if(present >= fig.getFigFinishDeliveryTime().floatValue()){
//                                            
//                                            //부품조달이 없거나 부품 조달이 끝났을 때에는 1-MTTR impact(참고자료 1-2 3번 케이스)를 합니다.
//                                        	figuresAvailable = new BigDecimal(1f-fig.getFigImpRepair().floatValue());
//                                            //부품조달 시점을 초기화 합니다.
//                                            fig.setFigFinishDeliveryTime(new BigDecimal(0f));
//                                            
//                                        }
                                        
                                    //End of else syntax at Connect
                                    }else{
                                        MTTR = 0f;
                                    }
                                    
                                //End of else syntax at compare random number with hazard rate
                                }
                                
                                mttr.add(MTTR);
                            
                            // End if syntax at isFigStates
                            }else{
                                
                                //현재시점이 수리가 끝나는 시점보다 작다면(아직 수리중이라면)
                                if(present < fig.getFigFinishRepairTime().floatValue()){
                                    
                                    //고장난 현재 시점을 부품조달 시간과 비교합니다.
                                    if(present < fig.getFigFinishDeliveryTime().floatValue()){
                                        
                                        //부품조달 중에는 1-MTTF impact(참고자료 1-2 2번 케이스)를 합니다.
                                        figuresAvailable = new BigDecimal(1f-fig.getFigImpFailure().floatValue());
                                        
                                    //고장난 현재 시점이 부품조달 시간보다 크거나 같다면 즉 배달시간이 없거나 배달을 했다면
                                    }else if(present >= fig.getFigFinishDeliveryTime().floatValue()){
                                        
                                        //부품조달이 없거나 부품 조달이 끝났을 때에는 1-MTTR impact(참고자료 1-2 3번 케이스)를 합니다.
                                        figuresAvailable = new BigDecimal(1f-fig.getFigImpRepair().floatValue());
                                        //부품조달 시점을 초기화 합니다.
                                        fig.setFigFinishDeliveryTime(new BigDecimal(0f));
                                        
                                    }
                                
                                //현재시점이 수리가 끝나는 시점보다 크다면(즉 수리가 끝났다면)
                                }else if(present >= fig.getFigFinishRepairTime().floatValue()){

                                    //수리종료 시점을 초기화 합니다.
                                    fig.setFigFinishRepairTime(new BigDecimal(0));
                                    
                                    //부품조달 시점을 초기화 합니다.
                                    fig.setFigFinishDeliveryTime(new BigDecimal(0));
                                    
                                    //Fail Mode의 상태를 정상으로 변환합니다.
                                    fig.setFigStates(true);
                                    
                                    /*
                                     * 고장 시점에 저장한 자투리 시간이 존재한다면
                                     * 자투리 시간을 1루의 생산량에 미치는 영향 대한 계산을 합니다. 
                                     */
                                    if(fig.getFigPieceRepairTime().floatValue() > 0f){
                                        
                                        /*
                                         * 자투리 시간이 Time Division 차지하는 백분율을 계산하고(자투리시간/Time Division)
                                         * 정상적인 Time Division 의 백분율을 계산하여(1/Time Division)
                                         * 이 두 값의 비율(자투리 비율/정상의 비율)을 다시 구한 다음 1에서 뺴면 자투리 시간에 대한 가용율이 나옵니다.
                                         */
                                        figuresAvailable = new BigDecimal((1f-(fig.getFigPieceRepairTime().floatValue()/getTimeDiv())/(1/getTimeDiv())));
                                        
                                        //자투리 시간을 초기화 합니다.
                                        fig.setFigPieceRepairTime(new BigDecimal(0));
                                       
                                    //자투리 시간이 존재하지 않는다면
                                    }else if(fig.getFigPieceRepairTime().floatValue() <= 0f){

                                        //자투리 시간이 없거나, 작다면 bd를 초기화 합니다.
                                        figuresAvailable = new BigDecimal(1f);
                                        //자투리 시간을 초기화 합니다.
                                        fig.setFigPieceRepairTime(new BigDecimal(0));
                                    
                                    //End of if syntax at pieceRepairTime
                                    }
                                
                                //End of if syntax at capre psresent with finishRepairTime
                                }
                            
                            // End else syntax at isFigStates
                            }
                            
                            systemState *= systemState;
                            figIndx++;
                            
                            equipmentAvailable = equipmentAvailable.multiply(figuresAvailable);
                            
                        //End of For syntax at Figures
                        }
                        
                        
                        /*
                         * 장비당 가용율을 저장하는 부분입니다.
                         * 
                         * 만약에 Reporting을 위해서 구해야 할 것이 있다면
                         * 이부분에서나 이 부분 이전에 Code에서 찾아서 변경하거나 추가 하면 됩니다.
                         */
                        availableEquipmentList.add(new AvailableFields(mod.getMoTagId(), equipmentAvailable, new BigDecimal(1f-equipmentAvailable.floatValue())));
                        
                        //계산된 Figure의 데이터(breakPoint, isFigStates, finishDeliveryTime and finishRepairTiem)를 다시 입력합니다. 
                        mod.setFiguresVector(figVector);
                    
                    //End of if syntax at modeling type 'eq'
                    }
                    
                //End of For syntax at Equipments
                }
                
                
                //Initialize ArrayList Object for memory
                mttr = null;
                
                //모든 시간은 1/time division한 것 만큼 증가합니다.
                present += (1f/getTimeDiv());
                
            //End of While syntax
            }
            
            float aver = 0f;
            for(AvailableFields av : availableEquipmentList){
                aver = av.getAvailable().floatValue()+aver;
            }
            
//            log.debug("Available Average is "+((aver/(float)availableEquipmentList.size())*100f));
            availableAverageList.add(new BigDecimal(((aver/(float)availableEquipmentList.size())*100f)));
            
            /*
             * 부모 클래스에 각 장비의 FM마다 생성되는
             * Hazard Rate와 난수를 저장합니다.
             * 시뮬레이션 실행 화면에서 필요한 부분입니다.
             */
            super.returnHazardRandom(equipInform);
            
            //각각의 Operating Time을 저장합니다.
            presentList.add(present);
            
            //저장된 Operating Time의 회수만큼 Float Arrays를 선언합니다.
            Float[] timeSim = new Float[presentList.size()];
            
            //Converting ArrayList with Arrays
            presentList.toArray(timeSim);
            
            //Initialize ArrayList Object for memory
            presentList = null;
            
            //시뮬레이션 횟수를 저장합니다.
            simFields.setIterationStep(strtSim);
            
            //수리시간을 저장합니다. - 가용성 로직을 변경하면서 수정이 필요하게 되었습니다.
            simFields.setRepairTime(0);
            
            //가용성을 측정하여 저장합니다.
            simFields.setAvailabilityValue((aver/(float)availableEquipmentList.size())*100f);
            
            //Operating Time의 Arrays를 저장합니다.
            simFields.setTimeOfSimulation(timeSim);
            
            //SimFields를 SimFields Arrays에 저장합니다.
            simFieldsArr[strtSim] = simFields;
            
            //Initialize Object for memory
            simFields = null;
            
            //Initiate figures break point, recover factor per Equipment
            for(Modeling modeling : modelingVector){
                
                Vector<Figures> figVector = modeling.getFiguresVector();
                for(Figures fig : figVector){
                    fig.setFigBreakPoint(0f);
                    fig.setFigRecovFactorIniti();
                }
                modeling.setFiguresVector(figVector);
            
            //End of for syntax at initiate    
            }
            
        //End of for syntax
        }
        
        BigDecimal aveAvailable = new BigDecimal(0f);
        for(BigDecimal aveList : availableAverageList){
            aveAvailable = aveAvailable.add(aveList);
        }
        log.debug("Ave Availability :   "+(aveAvailable.floatValue()/(float)availableAverageList.size()));
        
        //시뮬레이션이 끝나는 시점의 시간을 구합니다.
        long end = System.currentTimeMillis();
        
        //시뮬레이션 시작과 끝시점의 차이고 시뮬레이션이 몇초가 걸렸는지 표현합니다.
        log.debug( "실행 시간 : " + ( end - start )/1000.0 + "초" );
        
        //SimFields Arrays를 반환합니다.
        return simFieldsArr;
    }
    
}